diff -r 3501986de11d -r ca972efc51fc Lib/ntpath.py --- a/Lib/ntpath.py Sun May 31 21:44:27 2009 +0200 +++ b/Lib/ntpath.py Sun May 31 16:29:08 2009 -0400 @@ -306,13 +306,19 @@ return split(p)[0] # Is a path a symbolic link? -# This will always return false on systems where posix.lstat doesn't exist. +# This will always return false on systems where os.lstat doesn't exist. def islink(path): - """Test for symbolic link. - On WindowsNT/95 and OS/2 always returns false """ - return False + Test whether a path is a symbolic link. + This will always return false for Windows prior to 6.0 + and for OS/2. + """ + try: + st = os.lstat(path) + except (os.error, AttributeError): + return False + return stat.S_ISLNK(st.st_mode) # alias exists to lexists lexists = exists diff -r 3501986de11d -r ca972efc51fc Modules/posixmodule.c --- a/Modules/posixmodule.c Sun May 31 21:44:27 2009 +0200 +++ b/Modules/posixmodule.c Sun May 31 16:29:08 2009 -0400 @@ -618,7 +618,7 @@ #ifdef MS_WINDOWS static PyObject * -win32_error(char* function, char* filename) +win32_error(char* function, const char* filename) { /* XXX We should pass the function name along in the future. (winreg.c also wants to pass the function name.) @@ -1003,6 +1003,45 @@ return 0; } +void set_symlink_statA( + const char *path, + WIN32_FILE_ATTRIBUTE_DATA *info, + struct win32_stat *result) +{ + /* Get WIN32_FIND_DATA structure for the path to determine if + it is a symlink */ + WIN32_FIND_DATAA find_data; + HANDLE find_data_handle; + if(!(info->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) return; + find_data_handle = FindFirstFileA(path, &find_data); + if(find_data_handle != INVALID_HANDLE_VALUE) + { + if(find_data.dwReserved0 == IO_REPARSE_TAG_SYMLINK) + result->st_mode |= 0120000; + FindClose(find_data_handle); + } +} + +void set_symlink_statW( + const wchar_t *path, + WIN32_FILE_ATTRIBUTE_DATA *info, + struct win32_stat *result) +{ + /* Get WIN32_FIND_DATA structure for the path to determine if + it is a symlink */ + WIN32_FIND_DATAW find_data; + HANDLE find_data_handle; + if(!(info->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) return; + find_data_handle = FindFirstFileW(path, &find_data); + if(find_data_handle != INVALID_HANDLE_VALUE) + { + if(find_data.dwReserved0 == IO_REPARSE_TAG_SYMLINK) + result->st_mode |= 0120000; + FindClose(find_data_handle); + } +} + + /* Emulate GetFileAttributesEx[AW] on Windows 95 */ static int checked = 0; static BOOL (CALLBACK *gfaxa)(LPCSTR, GET_FILEEX_INFO_LEVELS, LPVOID); @@ -1115,8 +1154,25 @@ return attributes_from_dir_w(pszFile, pfad); } +/* +About the following functions: win32_lstat, win32_lstat_w, + win32_stat, win32_stat_w + + In Posix, stat automatically traverses symlinks and returns + the stat structure for the target. In Windows, the equivalent + GetFileAttributes by default does not traverse symlinks and + instead returns attributes for the symlink. + + Therefore, win32_lstat will get the attributes traditionally, + and win32_stat will first explicitly resolve the symlink target + and then will call win32_lstat on that result. + + The _w represent Unicode equivalents of the aformentioned ANSI + functions. +*/ + static int -win32_stat(const char* path, struct win32_stat *result) +win32_lstat(const char* path, struct win32_stat *result) { WIN32_FILE_ATTRIBUTE_DATA info; int code; @@ -1141,6 +1197,9 @@ code = attribute_data_to_stat(&info, result); if (code != 0) return code; + + set_symlink_statA(path, &info, result); + /* Set S_IFEXEC if it is an .exe, .bat, ... */ dot = strrchr(path, '.'); if (dot) { @@ -1154,7 +1213,7 @@ } static int -win32_wstat(const wchar_t* path, struct win32_stat *result) +win32_lstat_w(const wchar_t* path, struct win32_stat *result) { int code; const wchar_t *dot; @@ -1179,6 +1238,9 @@ code = attribute_data_to_stat(&info, result); if (code < 0) return code; + + set_symlink_statW(path, &info, result); + /* Set IFEXEC if it is an .exe, .bat, ... */ dot = wcsrchr(path, '.'); if (dot) { @@ -1191,6 +1253,117 @@ return code; } +/* Grab GetFinalPathNamyByHandle dynamically from kernel32 */ +static int has_GetFinalPathNameByHandle = 0; +static DWORD (CALLBACK *Py_GetFinalPathNameByHandleA)(HANDLE, LPSTR, DWORD, DWORD); +static DWORD (CALLBACK *Py_GetFinalPathNameByHandleW)(HANDLE, LPWSTR, DWORD, DWORD); +static int +check_GetFinalPathNameByHandle() +{ + HINSTANCE hKernel32; + /* only recheck */ + if (!has_GetFinalPathNameByHandle) + { + hKernel32 = GetModuleHandle("KERNEL32"); + *(FARPROC*)&Py_GetFinalPathNameByHandleA = GetProcAddress(hKernel32, "GetFinalPathNameByHandleA"); + *(FARPROC*)&Py_GetFinalPathNameByHandleW = GetProcAddress(hKernel32, "GetFinalPathNameByHandleW"); + has_GetFinalPathNameByHandle = Py_GetFinalPathNameByHandleA && Py_GetFinalPathNameByHandleW; + } + return has_GetFinalPathNameByHandle; +} + +static int +win32_stat(const char* path, struct win32_stat *result) +{ + /* Traverse the symlink to the target using + GetFinalPathNameByHandle() + */ + int code; + HANDLE hFile; + int buf_size; + char *target_path; + int result_length; + + if(!check_GetFinalPathNameByHandle()) + { + return win32_lstat(path, result); + } + + hFile = CreateFileA( + path, + 0, /* desired access */ + 0, /* share mode */ + NULL, /* security attributes */ + OPEN_EXISTING, + /* FILE_FLAG_BACKUP_SEMANTICS is required to open a directory */ + FILE_ATTRIBUTE_NORMAL|FILE_FLAG_BACKUP_SEMANTICS, + NULL); + + if(hFile == INVALID_HANDLE_VALUE) + { + return -1; + } + + buf_size = Py_GetFinalPathNameByHandleA(hFile, 0, 0, VOLUME_NAME_DOS); + if(!buf_size) return -1; + target_path = (char *)malloc((buf_size+1)*sizeof(char)); + result_length = Py_GetFinalPathNameByHandleA(hFile, target_path, buf_size, VOLUME_NAME_DOS); + + if(!result_length) return -1; + if(!CloseHandle(hFile)) return -1; + target_path[result_length] = 0; + code = win32_lstat(path, result); + free(target_path); + + return code; +} + +static int +win32_stat_w(const wchar_t* path, struct win32_stat *result) +{ + /* Traverse the symlink to the target using + GetFinalPathNameByHandle() + */ + int code; + HANDLE hFile; + int buf_size; + wchar_t *target_path; + int result_length; + + if(!check_GetFinalPathNameByHandle()) + { + return win32_lstat_w(path, result); + } + + hFile = CreateFileW( + path, + 0, /* desired access */ + 0, /* share mode */ + NULL, /* security attributes */ + OPEN_EXISTING, + /* FILE_FLAG_BACKUP_SEMANTICS is required to open a directory */ + FILE_ATTRIBUTE_NORMAL|FILE_FLAG_BACKUP_SEMANTICS, + NULL); + + if(hFile == INVALID_HANDLE_VALUE) + { + return -1; + } + + buf_size = Py_GetFinalPathNameByHandleW(hFile, 0, 0, VOLUME_NAME_DOS); + if(!buf_size) return -1; + target_path = (wchar_t *)malloc((buf_size+1)*sizeof(wchar_t)); + result_length = Py_GetFinalPathNameByHandleW(hFile, target_path, buf_size, VOLUME_NAME_DOS); + + if(!result_length) return -1; + if(!CloseHandle(hFile)) return -1; + target_path[result_length] = 0; + code = win32_lstat_w(path, result); + free(target_path); + + return code; +} + static int win32_fstat(int file_number, struct win32_stat *result) { @@ -2793,7 +2966,7 @@ posix_stat(PyObject *self, PyObject *args) { #ifdef MS_WINDOWS - return posix_do_stat(self, args, "O&:stat", STAT, "U:stat", win32_wstat); + return posix_do_stat(self, args, "O&:stat", STAT, "U:stat", win32_stat_w); #else return posix_do_stat(self, args, "O&:stat", STAT, NULL, NULL); #endif @@ -4658,7 +4831,7 @@ return posix_do_stat(self, args, "O&:lstat", lstat, NULL, NULL); #else /* !HAVE_LSTAT */ #ifdef MS_WINDOWS - return posix_do_stat(self, args, "O&:lstat", STAT, "U:lstat", win32_wstat); + return posix_do_stat(self, args, "O&:lstat", win32_lstat, "U:lstat", win32_lstat_w); #else return posix_do_stat(self, args, "O&:lstat", STAT, NULL, NULL); #endif @@ -4735,6 +4908,78 @@ } #endif /* HAVE_SYMLINK */ +#if !defined(HAVE_SYMLINK) && defined(MS_WINDOWS) + +/* Grab CreateSymbolicLinkW dynamically from kernel32 */ +static int has_CreateSymbolicLinkW = 0; +static DWORD (CALLBACK *Py_CreateSymbolicLinkW)(LPWSTR, LPWSTR, DWORD); +static int +check_CreateSymbolicLinkW() +{ + HINSTANCE hKernel32; + /* only recheck */ + if (has_CreateSymbolicLinkW) + return has_CreateSymbolicLinkW; + hKernel32 = GetModuleHandle("KERNEL32"); + *(FARPROC*)&Py_CreateSymbolicLinkW = GetProcAddress(hKernel32, "CreateSymbolicLinkW"); + if (Py_CreateSymbolicLinkW) + has_CreateSymbolicLinkW = 1; + return has_CreateSymbolicLinkW; +} + +PyDoc_STRVAR(win_symlink__doc__, +"symlink(src, dest, target_is_directory=False)\n\n\ +Create a symbolic link pointo to src named dst.\n\n\ +target_is_directory is required if the target is to be interpreted as\n\n\ +a directory.\\\ +This function requires Windows 6.0 or greater, and raises a\n\n\ +NotImplementedError otherwise."); + +static PyObject * +win_symlink(PyObject *self, PyObject *args, PyObject *kwargs) +{ + static char *kwlist[] = {"src", "dest", "target_is_directory", NULL}; + PyObject *src, *dest; + int target_is_directory = 0; + DWORD res; + + if (!check_CreateSymbolicLinkW()) + { + /* raise NotImplementedError */ + return PyErr_Format(PyExc_NotImplementedError, + "CreateSymbolicLinkW not found"); + } + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OO|i:symlink", + kwlist, &src, &dest, &target_is_directory)) + return NULL; + if (!convert_to_unicode(&src)) { return NULL; } + if (!convert_to_unicode(&dest)) { + Py_DECREF(src); + return NULL; + } + /* TODO: if dest is a directory, ensure target_is_directory==1 + Something like: + target_is_directory ||= ntpath.isdir(dest); + + only ntpath doesn't exist in this context. + */ + Py_BEGIN_ALLOW_THREADS + res = Py_CreateSymbolicLinkW( + PyUnicode_AsUnicode(src), + PyUnicode_AsUnicode(dest), + target_is_directory); + Py_END_ALLOW_THREADS + Py_DECREF(src); + Py_DECREF(dest); + if (!res) + { + return win32_error_unicode("symlink", PyUnicode_AsUnicode(src)); + } + + Py_INCREF(Py_None); + return Py_None; +} +#endif /* !defined(HAVE_SYMLINK) && defined(MS_WINDOWS) */ #ifdef HAVE_TIMES #if defined(PYCC_VACPP) && defined(PYOS_OS2) @@ -7099,6 +7344,9 @@ #ifdef HAVE_SYMLINK {"symlink", posix_symlink, METH_VARARGS, posix_symlink__doc__}, #endif /* HAVE_SYMLINK */ +#if !defined(HAVE_SYMLINK) && defined(MS_WINDOWS) + {"symlink", (PyCFunction)win_symlink, METH_VARARGS | METH_KEYWORDS, win_symlink__doc__}, +#endif /* !defined(HAVE_SYMLINK) && defined(MS_WINDOWS) #ifdef HAVE_SYSTEM {"system", posix_system, METH_VARARGS, posix_system__doc__}, #endif